package ru.mipt.testload.util;

import com.sun.xml.xsom.*;
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;
import org.w3c.dom.*;

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreePath;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.util.*;

/**
 * @author Pavel G. Belevich
 *         Date: 05.06.2010
 *         Time: 22:36:49
 */
public class XsdTreeTableModel extends AbstractTreeTableModel {

    private XSSchema xsSchema;
    private Document document;
    private Map<Element, XSElementDecl> xsElementDeclByElement;
    private Map<Attr, XSAttributeDecl> xsAttributeDeclByAttr;

    public XsdTreeTableModel(XSSchema xsSchema, Document document) {
        this.xsSchema = xsSchema;
        this.document = document;
        this.xsElementDeclByElement = new HashMap<Element, XSElementDecl>();
        this.xsAttributeDeclByAttr = new HashMap<Attr, XSAttributeDecl>();
        this.addTreeModelListener(new TreeModelListener() {
            public void treeNodesChanged(TreeModelEvent e) {
                printDocument();
            }

            public void treeNodesInserted(TreeModelEvent e) {
                printDocument();
            }

            public void treeNodesRemoved(TreeModelEvent e) {
                printDocument();
            }

            public void treeStructureChanged(TreeModelEvent e) {
                printDocument();
            }
        });
    }

    @Override
    public Object getRoot() {
        return document.getDocumentElement();
    }

    public static final String[] COLUMN_NAMES = new String[]{"Element", "Type", "Value"};
    public static final Class[] COLUMN_CLASSES = new Class[]{String.class, XSType.class, Node.class};

    public int getColumnCount() {
        return COLUMN_NAMES.length;
    }

    @Override
    public String getColumnName(int column) {
        return COLUMN_NAMES[column];
    }

    public Object getValueAt(Object o, int column) {
        if (o instanceof Node) {
            Node node = (Node) o;
            if (Node.ELEMENT_NODE == node.getNodeType()) {
                Element element = (Element) node;
                switch (column) {
                    case 0:
                        return element.getTagName();
                    case 1:
                        return getXSElementDeclByElement(element).getType();
                    case 2:
                        XSType xsType = getXSElementDeclByElement(element).getType();
                        if (xsType.isSimpleType()) {
                            return element.getTextContent();
                        } else {
                            return null;
                        }
                }
            } else if (Node.ATTRIBUTE_NODE == node.getNodeType()) {
                Attr attr = (Attr) node;
                switch (column) {
                    case 0:
                        return attr.getNodeName();
                    case 1:
                        return xsAttributeDeclByAttr.get(attr).getType();
                    case 2:
                        return attr.getNodeValue();
                }
            }
        }
        return null;
    }

    @Override
    public void setValueAt(Object value, Object o, int column) {
        if (o instanceof Node) {
            Node node = (Node) o;
            String s = String.valueOf(value);
            if (Node.ELEMENT_NODE == node.getNodeType()) {
                Element element = (Element) node;
                switch (column) {
                    case 2:
                        element.setTextContent(s);
                }
            } else if (Node.ATTRIBUTE_NODE == node.getNodeType()) {
                Attr attr = (Attr) node;
                switch (column) {
                    case 2:
                        attr.setNodeValue(s);
                }
            }
        }
        printDocument();
    }

    @Override
    public Class<?> getColumnClass(int column) {
        return COLUMN_CLASSES[column];
    }

    @Override
    public boolean isCellEditable(Object o, int column) {
        if (column == 2) {
            Node node = (Node) o;
            if (Node.ELEMENT_NODE == node.getNodeType()) {
                Element element = (Element) node;
                XSElementDecl xsElementDecl = getXSElementDeclByElement(element);
                XSType xsType = xsElementDecl.getType();
                return xsType.isSimpleType();
            } else if (Node.ATTRIBUTE_NODE == node.getNodeType()) {
                return true;
            }
        }
        return false;
    }

    public Object getChild(Object parent, int index) {
        if (parent instanceof Element) {
            Element parentElement = (Element) parent;
            NamedNodeMap map = parentElement.getAttributes();
            if (index < map.getLength()) {
                return map.item(index);
            } else {
                return parentElement.getChildNodes().item(index - map.getLength());
            }
        }
        return null;
    }

    public int getChildCount(Object parent) {
        if (parent instanceof Element) {
            Element parentElement = (Element) parent;
            NodeList childNodes = parentElement.getChildNodes();
            int childrenCount = 0;
            for (int i = 0 ; i < childNodes.getLength() ; i++) {
                Node node = childNodes.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    childrenCount++;
                }
            }
            return childrenCount + parentElement.getAttributes().getLength();
        }
        return 0;
    }

    public int getIndexOfChild(Object parent, Object child) {
        return 0;
    }

    public XSSchema getXsSchema() {
        return xsSchema;
    }

    public void createRootElement(XSElementDecl elementDecl) {
        Element root = document.createElement(elementDecl.getName());
        xsElementDeclByElement.put(root, elementDecl);
        afterCreateElement(root);
        document.appendChild(root);
        modelSupport.fireNewRoot();
    }

    public void addElement(Element parentElement, XSElementDecl elementDecl) {
        Element element = document.createElement(elementDecl.getName());
        xsElementDeclByElement.put(element, elementDecl);
        afterCreateElement(element);
        parentElement.appendChild(element);
        modelSupport.fireNewRoot();

//        try {
//            TransformerFactory transformerFactory = TransformerFactory.newInstance();
//            Transformer transformer = transformerFactory.newTransformer(new StreamSource(XsdTreeTableModel.class.getClassLoader().getResource("ru/mipt/testload/xml2json.xslt").getFile()));
//            transformer.transform(new DOMSource(document),
//                    new StreamResult(System.out));
//        } catch (Throwable e) {
//            e.printStackTrace();
//        }

    }

    private void printDocument() {
        try {
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            Source source = new DOMSource(document);
            Result output = new StreamResult(System.out);
            transformer.transform(source, output);
        } catch (TransformerException e) {
            e.printStackTrace();
        }
        System.out.println();
    }

    protected void afterCreateElement(Element element) {
        XSElementDecl xsElementDecl = getXSElementDeclByElement(element);
        XSType xsType = xsElementDecl.getType();
        if (xsType.isSimpleType()) {
            XSSimpleType xsSimpleType = xsType.asSimpleType();
            if (xsSimpleType.isRestriction()) {
                XSRestrictionSimpleType xsRestrictionSimpleType = xsSimpleType.asRestriction();
                for (XSFacet xsFacet : xsRestrictionSimpleType.getDeclaredFacets()) {
                    //TODO
                }
            }
        } else if (xsType.isComplexType()) {
            XSComplexType xsComplexType = xsType.asComplexType();
            XSContentType xsContentType = xsComplexType.getContentType();
            XSParticle xsParticle = xsContentType.asParticle();
            if (xsParticle != null) {
                XSTerm xsTerm = xsParticle.getTerm();
                if (xsTerm.isModelGroup()) {
                    XSModelGroup xsModelGroup = xsTerm.asModelGroup();
                    for (XSParticle childXsParticle : xsModelGroup.getChildren()) {
                        int minOccurs = childXsParticle.getMinOccurs();
                        XSTerm childXsTerm = childXsParticle.getTerm();
                        if (childXsTerm.isElementDecl()) {
                            XSElementDecl childXsElementDecl = childXsTerm.asElementDecl();
                            for (int i = 0; i < minOccurs; i++) {
                                addElement(element, childXsElementDecl);
                            }
                        } else if (childXsTerm.isModelGroup()) {
                            System.out.println("");
                            //TODO
                        } else if (childXsTerm.isModelGroupDecl()) {
                            System.out.println("");
                            //TODO
                        } else if (childXsTerm.isWildcard()) {
                            System.out.println("");
                            //TODO
                        }
                    }
                } else if (xsTerm.isElementDecl()) {
                    //TODO
                } else if (xsTerm.isModelGroupDecl()) {
                    //TODO
                } else if (xsTerm.isWildcard()) {
                    //TODO
                }
            }
            for (XSAttributeUse xsAttributeUse : xsComplexType.getAttributeUses()) {
                if (xsAttributeUse.isRequired()) {
                    XSAttributeDecl xsAttributeDecl = xsAttributeUse.getDecl();
                    setAttribute(element, xsAttributeDecl);
                }
            }
        }
    }

    public void setAttribute(Element element, XSAttributeDecl xsAttributeDecl) {
        Attr attr = document.createAttribute(xsAttributeDecl.getName());
        xsAttributeDeclByAttr.put(attr, xsAttributeDecl);
        element.setAttributeNode(attr);
    }

    public XSElementDecl getXSElementDeclByElement(Element element) {
        return xsElementDeclByElement.get(element);
    }

    public XSAttributeDecl getXSAttributeDeclByAttr(Attr attr) {
        return xsAttributeDeclByAttr.get(attr);
    }

    public Collection<XSElementDecl> getRootXSElementDecls() {
        Collection<XSElementDecl> childXSElementDecls = new ArrayList<XSElementDecl>();

        Iterator i = xsSchema.iterateElementDecls();
        while (i.hasNext()) {
            XSElementDecl xsElementDecl = (XSElementDecl) i.next();
            childXSElementDecls.add(xsElementDecl);
        }

        return childXSElementDecls;
    }

    public Collection<XSElementDecl> getChildXSElementDeclsForXSElementDecl(XSElementDecl xsElementDecl) {
        Collection<XSElementDecl> childXSElementDecls = new ArrayList<XSElementDecl>();

        XSType xsType = xsElementDecl.getType();
        if (xsType.isComplexType()) {
            XSComplexType xsComplexType = xsType.asComplexType();
            XSContentType xsContentType = xsComplexType.getContentType();
            XSParticle xsParticle = xsContentType.asParticle();
            XSSimpleType xsSimpleType = xsContentType.asSimpleType();
            XSContentType xsContentType1 = xsContentType.asEmpty();
            if (xsParticle != null) {
                XSTerm term = xsParticle.getTerm();
                if (term.isElementDecl()) {
                    //TODO
                } else if (term.isModelGroup()) {
                    XSModelGroup xsModelGroup = term.asModelGroup();
                    XSParticle[] children = xsModelGroup.getChildren();
                    for (XSParticle child : children) {
                        XSTerm xsTerm = child.getTerm();
                        if (xsTerm.isElementDecl()) {
                            XSElementDecl xsElementDeclChild = xsTerm.asElementDecl();
                            childXSElementDecls.add(xsElementDeclChild);
                        }
                    }
                } else if (term.isModelGroupDecl()) {
                    //TODO
                } else if (term.isWildcard()) {
                    //TODO
                }
            } else if (xsSimpleType != null) {
                //TODO
            } else if (xsContentType1 != null) {
                //TODO
            }

        }

        return childXSElementDecls;
    }

    public Collection<XSElementDecl> getChildXSElementDeclsForElement(Element element) {

        XSElementDecl xsElementDecl = getXSElementDeclByElement(element);

        return getChildXSElementDeclsForXSElementDecl(xsElementDecl);

    }

}
